/*
 * @Author: Lwz
 * @Coding: utf-8
 * @Date: 2019-12-20
 */
import cloneDeep from 'lodash/cloneDeep';

/*
* 事件委托封装：
*
*  一、为什么需要事件委托？
*     1.提高页面性能
*     2.有时候想要为某元素绑定监听事件，无法获取到某DOM元素，但可以获取其父级(祖先)元素。利用事件委托即可绑定事件
*  二、jQuery不是有 on() 方法可以实现事件委托吗？为什么还需要这个 onDelegate() 方法？
*     进入React、Vue、Angular的前端组件化 + 前端工程化时代，我们应该改变思维，在开发中尽量不要使用jQuery
*     你应该首选使用React提供的事件处理机制，尽量不要使用原生JS处理事件。当你确认React的事件处理无法满足你的需求、或者不方便实现时，可以
*     使用addEventListener()。虽然这里封装了 onDelegate()，但还是建议你不在万不得已的情况下不要使用。
*  三、文档
*     onDelegate()
*        1.参数：
*           events      【必需 string】事件类型
*           parent      【必需 DOM对象】被委托的DOM对象
*           selector    【必需 string】目标元素的CSS选择器（与jquery选择器类似的写法）
*           func        【必需 function】事件回调
*           data        【非必需 object】事件回调的传参
*           reverseScope【非必需 boolean】是否反向区域。默认值: false。【默认情况下，事件发生在selector目标元素时触发回调。
*                                                          此值为true时，只有事件发生在parent之内selector之外时才触发回调】
*           capture     【非必需 boolean】事件回调执行模式，false表示冒泡阶段执行，true表示捕捉阶段执行。默认false
*        2.回调函数的参数：
*           event 事件对象。
*           {
*             delegateTarget    被委托的DOM元素
*             currentTarget     当前触发的目标DOM元素。当reverseScope为false时为某个selector元素，reverseScope为true时为当前触发的元素。
*             data              onDelegate()中的data参数
*             originalEvent     原生事件对象e
*           }
*
*     offDelegate()
*        参数：
*           events      【必需 string】事件类型
*           parent      【必需 DOM对象】被委托的DOM对象
*           selector    【必需 string】目标元素的CSS选择器（与jquery选择器类似的写法）
*           func        【必需 function】事件回调
*           reverseScope【非必需 boolean】是否反向区域。默认值: false。
*
* */

const throwError = (message) => {
 throw new Error(message); 
};

// 判断是否是DOM元素
const isDOM = obj => (typeof HTMLElement === 'object' ?
      obj instanceof HTMLElement
      :
      obj && typeof obj === 'object' && obj.nodeType === 1 && typeof obj.nodeName === 'string');

// 检查selector的有效性
const checkSelector = (parent, selector) => {
   try {
      parent.querySelector(selector);
   } catch (e) {
      return '参数 selector 无效';
   }
};


// 参数检测
const paramCheck = (type, events, parent, selector, func, data, reverseScope, capture) => {
   let baseMsg = `Document模块 ${type}Delegate()方法调用错误：`;

   if (type === 'on') {
      typeof events !== 'string' && throwError(`${baseMsg}参数 events 必须是 string 类型，现在是${typeof events}！`);
      events.length === 0 && throwError(`${baseMsg}参数 events 不能为空！`);
      !isDOM(parent) && throwError(`${baseMsg}参数 parent 必须是 DOM 元素！`);
      typeof selector !== 'string' && throwError(`${baseMsg}参数 selector 必须是 string 类型，现在是${typeof selector}！`);
      let selectRes = checkSelector(parent, selector); // 检测selector的有效性
      typeof selectRes === 'string' && throwError(`${baseMsg}${selectRes}！`);
      typeof func !== 'function' && throwError(`${baseMsg}参数 func 必须是 function 类型，现在是${typeof func}！`);
      typeof reverseScope !== 'boolean' && throwError(`${baseMsg}参数 reverseScope 必须是 boolean 类型，现在是${typeof reverseScope}！`);
      typeof capture !== 'boolean' && throwError(`${baseMsg}参数 capture 必须是 boolean 类型，现在是${typeof capture}！`);
      Object.prototype.toString.call(data).slice(8, -1) !== 'Object' && throwError(`${baseMsg}参数 data 必须是 object 类型！`); // 判断data数据类型
   } else if (type === 'off') {
      typeof events !== 'string' && throwError(`${baseMsg}参数 events 必须是 string 类型，现在是${typeof events}！`);
      events.length === 0 && throwError(`${baseMsg}参数 events 不能为空！`);
      let selectRes = checkSelector(parent, selector); // 检测selector的有效性
      typeof selectRes === 'string' && throwError(`${baseMsg}${selectRes}！`);
      !isDOM(parent) && throwError(`${baseMsg}参数 parent 必须是 DOM元素！`);
      typeof selector !== 'string' && throwError(`${baseMsg}参数 selector 必须是 string 类型，现在是${typeof selector}！`);
      typeof func !== 'function' && throwError(`${baseMsg}参数 func 必须是 function 类型，现在是${typeof func}！`);
      typeof reverseScope !== 'boolean' && throwError(`${baseMsg}参数 reverseScope 必须是 boolean 类型，现在是${typeof reverseScope}！`);
   }
};


let EventHandles = [];

// 事件委托
const onDelegate = (events = '', parent, selector = '', func, data = {}, reverseScope = false, capture = false) => {
   data = cloneDeep(data);
   paramCheck('on', events, parent, selector, func, data, reverseScope, capture); // 参数检测

   const already = EventHandles.find(f => f.events === events && f.parent === parent && f.selector === selector && f.func === func && f.reverseScope === reverseScope);
   if (!already) {
      const handler = (e) => {
         let flag = false,
target = e.target,
selectList = Array.from(parent.querySelectorAll(selector));
         while (target.tagName !== 'BODY') {
            if (selectList.includes(target)) {
               let event = { delegateTarget: parent, currentTarget: target, data, originalEvent: e };
               !reverseScope && func(event);
               flag = true;
               break;
            }
            target = target.parentNode ? target.parentNode : '';
         }
         let event = { delegateTarget: parent, currentTarget: e.target, data, originalEvent: e };
         reverseScope && !flag && func(event);
      };
      parent.addEventListener(events, handler, capture);
      EventHandles.push({ events, parent, selector, func, reverseScope, handler });
   }
};

// 解除由onDelegate()绑定的事件监听
const offDelegate = (events = '', parent, selector = '', func, reverseScope = false) => {
   paramCheck('off', events, parent, selector, func, {}, reverseScope);
   let hands = EventHandles.filter(f => f.events === events && f.parent === parent && f.selector === selector && f.func === func && f.reverseScope === reverseScope);
   hands.forEach((i) => {
      parent.removeEventListener(events, i.handler);
      EventHandles.splice(EventHandles.indexOf(i), 1);
   });
};


export { onDelegate, offDelegate };

